package com.kazares.dao;

import com.kazares.anotaciones.Atributo;
import com.kazares.anotaciones.Columna;
import com.kazares.anotaciones.Entidad;
import com.kazares.anotaciones.Validacion;
import com.kazares.persistencia.DbInstance;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Alejandro Cazares Diaz
 */
public class UtilDao<DTO> {

    protected DTO instancia = null;
    protected String tabla;
    protected String esquema;
    protected HashMap<String, Atributo> atributos;

    /**
     * Constructor por defecto
     */
    public UtilDao() {
    }

    /**
     * Constructor parametrizado
     *
     * @param instancia Clase DTO con anotaciones de Entidad, columna y
     * validaciones
     */
    public UtilDao(DTO instancia) {
        setInstance(instancia);
    }

    /**
     * Establece una instancia para transformar un DTO a Entidad y viceversa
     *
     * @param instancia Clase DTO con anotaciones de Entidad, columna y
     * validaciones
     */
    public void setInstance(DTO instancia) {
        atributos = new HashMap<String, Atributo>();

        this.instancia = instancia;
        if (instancia != null) {
            Annotation definicion = instancia.getClass().getAnnotation(Entidad.class);
            //si la clase de referencia es un dto obtenemos sus anotaciones.
            if (definicion instanceof Entidad) {
                Entidad anotacionEntidad = (Entidad) definicion;
                tabla = anotacionEntidad.tabla();
                esquema = anotacionEntidad.esquema();

                //inspeccionamos cada metodo de la clase
                for (Method metodo : instancia.getClass().getMethods()) {
                    //Obtenemos el nombre del metodo
                    String nombreMetodo = metodo.getName();
                    //Obtenemos los tipos de dato de los parametros
                    Class tipos[] = metodo.getParameterTypes();

                    //filtramos y permitimos solo los metodos getter y setter
                    int tipo = nombreMetodo.substring(0, 3).equals("set") ? 2
                            : (nombreMetodo.substring(0, 3).equals("get")
                            || nombreMetodo.substring(0, 3).equals("is")) ? 1 : 0;

                    if (tipo == 1 || tipo == 2) {
                        //predecimos el nombre del atributo relacionado a un getter o setter (para usarlo como indice)
                        String atributo = nombreMetodo.replace("get", "").replace("set", "").replace("is", "").toLowerCase();

                        Atributo attr = atributos.get(atributo);
                        if (attr == null) {
                            attr = new Atributo(atributo);
                            atributos.put(atributo, attr);
                        }
                        //guardamos el metodo setter y el tipo de dato
                        if (tipos.length == 1 && tipo == 2) {
                            attr.setSetter(metodo);
                        }
                        //guardamos el metodo getter
                        if (tipos.length == 0 && tipo == 1) {
                            attr.setGetter(metodo);
                            try {
                                attr.setTipo(metodo.getReturnType());
                            } catch (Exception e) {
                                System.err.println("Error al obtener el tipo de dato de un atributo.");
                                attr.setTipo(null);
                            }
                            try {
                                attr.setValor(metodo.invoke(instancia));
                            } catch (Exception e) {
                                System.err.println("Error al obtener el valor de un atributo.");
                                attr.setValor(null);
                            }
                        }

                        //obtenemos todas las anotaciones del metodo
                        try {
                            Annotation[] anotaciones = metodo.getDeclaredAnnotations();
                            for (Annotation anotacion : anotaciones) {
                                //insertamos en la biblioteca de nombres de columnas
                                if (anotacion instanceof Columna) {
                                    Columna miAnotacion = (Columna) anotacion;
                                    attr.setCampo(miAnotacion.nombre());
                                }
                                //insertamos en la biblioteca de validaciones
                                if (anotacion instanceof Validacion) {
                                    Validacion miAnotacion = (Validacion) anotacion;
                                    attr.setValidaciones(miAnotacion.validaciones());
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                }//end foreach methods
            }//end if instance of Entidad
        }
    }

    public String create() throws SQLException {
        //construcción del query
        String strColumnas = atributos.get("id").getCampo();
        String strValores = "UUID()";
        String sql = "";

        for (String index : atributos.keySet()) {
            Atributo attr = atributos.get(index);
            if (attr.getCampo() != null && !attr.getCampo().equals("ID")) {
                strColumnas += attr.getCampo() + ",";
                // pendiente implementar validador
                if (attr.getValor() != null) {
                    strValores += ",'" + attr.getValor() + "'";
                } else {
                    strValores += ",''";
                }
            }
        }

        Connection conn = null;
        String pId = null;
        if (strColumnas.length() > 0) {
            sql = "INSERT INTO " + esquema + "." + tabla + " (" + strColumnas
                    + ") VALUES(" + strValores + ")";
            // System.out.println("SQL Crear: " + sql);

            try {
                conn = new DbInstance().getInstance();
                Statement st = conn.createStatement();
                st.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
                ResultSet rs = st.getGeneratedKeys();

                if (rs.next()) {
                    pId = rs.getString(atributos.get("id").getCampo());
                }
                conn.commit();
            } catch (Exception e) {
                System.err.println(sql);
                e.printStackTrace();
            } finally {
                conn.close();
            }
        }
        return pId;
    }

    public ArrayList<DTO> read() {
        ArrayList<DTO> dto = new ArrayList<DTO>();
        String whereClause = "";
        for (String key : atributos.keySet()) {
            if (atributos.get(key).getCampo() != null && atributos.get(key).getValor() !=null) {
                whereClause += " AND " + atributos.get(key).getCampo() + " = '" + 
                        atributos.get(key).getValor() + "'";
            }
        }
        try{
            read(0,1, whereClause);
        }catch(Exception e){
            e.printStackTrace();
        }
        



        return dto;
    }

    public DTO read(int id) throws SQLException {
        String sqlWhere = " AND " + atributos.get("id").getCampo() + " = '"
                + atributos.get("id").getValor() + "'";
        ArrayList<DTO> dto = read(0, 1, sqlWhere);
        return dto.size() > 0 ? dto.get(0) : null;
    }

    public ArrayList<DTO> read(int page, int size) throws SQLException {
        return read(page, size, "");
    }

    public ArrayList<DTO> read(int page, int size, String whereClause) throws SQLException {
        ArrayList<DTO> dtoList = new ArrayList<DTO>();

        String sql = " SELECT * FROM " + esquema + "." + tabla + " WHERE 1=1 "
                + whereClause + " LIMIT " + (page * size) + "," + size;
        System.out.println(sql);
        Connection conn = null;
        try {
            conn = new DbInstance().getInstance();
            PreparedStatement st = conn.prepareStatement(sql);
            ResultSet result = st.executeQuery();
            Class<DTO> nuevoObjeto = (Class<DTO>) instancia.getClass();
            while (result.next()) {
                DTO obj = nuevoObjeto.newInstance();
                for (String index : atributos.keySet()) {
                    if (atributos.get(index).getCampo() != null) {
                        Method setter = atributos.get(index).getSetter();
                        setter.invoke(obj, result.getObject(atributos.get(index).getCampo()));
                    }
                }
                dtoList.add(obj);
            }
        } catch (Exception e) {
            System.err.println(sql);
            e.printStackTrace();
        } finally {
            conn.close();
        }
        return dtoList;
    }

    public void update() throws SQLException {
        String strColumnas = "";
        for (String index : atributos.keySet()) {
            Atributo attr = atributos.get(index);
            if (attr.getCampo() != null) {
                // pendiente implementar validador
                if (attr.getValor() != null && !attr.getCampo().equals("ID")) {
                    strColumnas += attr.getCampo() + " = '" + attr.getValor() + "',";
                }
            }
        }

        String sql = "";
        if (strColumnas.length() > 0) {
            sql = "UPDATE " + esquema + "." + tabla + " SET " + strColumnas.substring(0, strColumnas.length() - 1);
            sql += " WHERE " + atributos.get("id").getCampo() + " = '" + atributos.get("id").getCampo() + "'";
            // System.out.println("SQL actualizar: " + sql);

            Connection conn = null;
            try {
                conn = new DbInstance().getInstance();
                Statement st = conn.createStatement();
                st.executeUpdate(sql);
                conn.commit();

            } catch (Exception e) {
                System.err.println(sql);
                e.printStackTrace();
            } finally {
                conn.close();
            }
        }
    }

    public void delete() throws SQLException {
        delete(atributos.get("id").getValor());
    }

    public void delete(Object id) throws SQLException {
        String sql = "DELETE FROM " + esquema + "." + tabla + " WHERE "
                + atributos.get("id").getCampo() + " = '" + id + "'";
        Connection conn = null;
        try {
            conn = new DbInstance().getInstance();
            Statement st = conn.createStatement();
            st.executeUpdate(sql);
            conn.commit();
        } catch (Exception e) {
            System.err.println(sql);
            e.printStackTrace();
        } finally {
            conn.close();
        }
    }

    public int size() throws SQLException {
        return size("");
    }

    public int size(String whereClause) throws SQLException {
        String sql = "SELECT COUNT(*) TOTAL FROM " + esquema + "." + tabla;
        Connection conn = null;
        try {
            conn = new DbInstance().getInstance();
            PreparedStatement st = conn.prepareStatement(sql);
            ResultSet result = st.executeQuery();
            while (result.next()) {
                return result.getInt("TOTAL");
            }
        } catch (Exception e) {
            System.err.println(sql);
            e.printStackTrace();
        } finally {
            conn.close();
        }
        return 0;
    }
    //1)obtener(id)
    //n)obtener()
    //1)actualizar
    //1)Eliminar
    //n)eliminar(where)
    //1)create
//    public int crear() throws SQLException {
//        int r = 0;
//        String strColumnas = "", strValores = "";
//        for (String index : atributos.keySet()) {
//            Atributo attr = atributos.get(index);
//            if (attr.getCampo() != null) {
//                strColumnas += attr.getCampo() + ",";
//                //vaidar campo
//                strValores += "'" + attr.getValor() + "',";
//            }
//        }
//        if (strColumnas.length() > 0) {
//            String sql = "INSERT INTO " + esquema + "." + tabla + " ("
//                    + strColumnas.substring(0, strColumnas.length() - 1) + ") VALUES("
//                    + strValores.substring(0, strValores.length() - 1) + ")";
//            System.out.println("SQL Crear: " + sql);
//
//            Connection conn = null;
//            try {
//                conn = new DbInstance().getInstance();
//                Statement statement = conn.createStatement();
//                r = statement.executeUpdate(sql);
//            } catch (Exception e) {
//                System.err.println("Error en metodo crear, detalles: " + e);
//            } finally {
//                conn.close();
//            }
//        }
//        return r;
//
//    }
//
//    public void decirTodo() {
//        for (String a : atributos.keySet()) {
//            try {
//                System.out.println("atributo " + atributos.get(a).getAtributo());
//                System.out.println("campo " + atributos.get(a).getCampo());
//                System.out.println("validaciones " + atributos.get(a).getValidaciones());
//                System.out.println("getter " + atributos.get(a).getGetter().getName());
//                System.out.println("getter returns " + atributos.get(a).getGetter().getReturnType().getName());
//                System.out.println("setter " + atributos.get(a).getSetter().getName());
//                System.out.println("tipo " + atributos.get(a).getTipo().getName());
//                System.out.println("valor " + atributos.get(a).getValor());
//            } catch (Exception e) {
//                System.out.println("Pequeño error: " + e);
//            }
//            System.out.println("------------------------------------------------");
//        }
//    }
//
//    public static void main(String[] args) {
//        Usuario user = new Usuario();
//        user.setId("cazares");
//        user.setCreacion("2013-02-08 21:55:08.0");
//        user.setUltimo_acceso("2013-02-08 21:55:08.0");
//        try {
//            //new UtilDao(user).decirTodo();
//
//            System.out.println("Update result: " + (new UtilDao(user)).crear());
//        } catch (SQLException ex) {
//            Logger.getLogger(UtilDao.class.getName()).log(Level.SEVERE, null, ex);
//        }
//    }
//
//    public Object obten() {
//        return null;
//    }
}
